<template>
<el-config-provider :locale="locale">
  <div class="m-snakerflow-designer" ref="lfElRef"></div>
  <PropertSetting
    ref="propertSettingRef"
    :extendAttrConfig="props.extendAttrConfig"
    :extendPropertyKeys="extendPropertyKeys"
    @change="handlePropertyChange">
    <template #[key]="data" v-for="(item, key) in $slots">
      <slot :name="key" v-bind="data || {}"></slot>
    </template>
  </PropertSetting>
  <DataDetail ref="dataDetailRef"/>
  <ImportData ref="importDataRef" @on-submit="handleImportSubmit"/>
  <HighLightData ref="highLightDataRef" @on-submit="handleHighLightSubmit"/>
</el-config-provider>
</template>
<script lang="ts" setup>
import LogicFlow from '@logicflow/core'
import { Snapshot, DndPanel, SelectionSelect, Menu, Control, Group } from '@logicflow/extension'
import '@logicflow/core/dist/style/index.css'
import '@logicflow/extension/lib/style/index.css'
import { onMounted, Ref, ref, unref, defineProps, watch, defineExpose, defineEmits, PropType, version } from 'vue'
import { ProcessModel, PropertyEvent, WfConfig } from './types'
import PropertSetting from './PropertySetting'
import type EventEmitter from '@logicflow/core/types/event/eventEmitter'
import { SnakerFlowElement, SnakerFlowAdapter } from './snakerflow/index'
import { NodeTypeEnum, ColorEnum } from './enums'
import DataDetail from './Control/DataDetail.vue'
import ImportData from './Control/ImportData.vue'
import HighLightData from './Control/HighLightData.vue'
import { snakerXml2LogicFlowJson } from './snakerflow/tool'
import { ElConfigProvider, version as EleVersion } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { patternItems } from './data'
const emits = defineEmits(['update:modelValue', 'on-save', 'on-init'])
const locale = ref(zhCn)
// 定义挂载元素Ref
const lfElRef:Ref = ref(null)
// 属性面板操作
const propertSettingRef: Ref = ref(null)
// 查看数据弹窗
const dataDetailRef: Ref = ref(null)
// 导入数据弹窗
const importDataRef: Ref = ref(null)
// 导入高高弹窗
const highLightDataRef: Ref = ref(null)
// 定义LogicFlow实例
const lfInstance = ref(null) as Ref<LogicFlow | null>
// 当前操作节点/或边id
const currentOpId = ref('')
// 定义组件接收的参数
const props = defineProps({
  modelValue: {
    type: [Object, String]
  },
  config: {
    type: Object,
    default () {
      return {
        grid: true
      }
    }
  },
  highLight: { // 高亮数据
    type: Object,
    default () {
      return {
      }
    }
  },
  assigneeText: { // 参与人回显文本
    type: [Object, Array],
    default () {
      return {
      }
    }
  },
  viewer: { // 预览模式
    type: Boolean,
    default: false
  },
  extendAttrConfig: { // 扩展属性配置
    type: Object
  },
  showDoc: { // 是否显示文档
    type: Boolean,
    default: true
  },
  // 扩展的属性key
  extendPropertyKeys: {
    type: Array
  },
  wfConfig: { // 本流程设计器特定的配置，区别于config，config为LogicFlow的配置
    type: Object as PropType<WfConfig>,
    default () {
      return {} as WfConfig
    }
  },
  nodeRenderType: { // 节点渲染类型，html 和svg
    type: String as PropType<'svg' | 'html'>,
    default: 'svg'
  },
  taskNodeRender: { // 自定义节点渲染
    type: Function
  }
})

// 监听流程变化
watch(() => props.modelValue, () => {
  reRender(props.modelValue)
}, {
  deep: true
})
// 监听高亮数据
watch(() => props.highLight, () => {
  setHighLight(props.highLight)
}, {
  deep: true
})
// 监听参与人回显数据
watch(() => props.assigneeText, () => {
  setAssigneeText(props.assigneeText)
}, {
  deep: true
})
const initDoc = () => {
  const lf = unref(lfInstance)
  if (!lf) return
  if (props.showDoc) {
    // 控制面板-码云
    lf.extension.control.addItem({
      iconClass: 'lf-control-mayu',
      title: '',
      text: '码云',
      onClick: () => {
        window.location.href = 'https://gitee.com/mldong/snakerflow-designer-vue/tree/vue3/'
      }
    })
  }
}
// 初始化
const init = () => {
  console.info(`Vue:${version}`)
  console.info(`element-plus:${EleVersion}`)
  // 画布配置
  LogicFlow.use(Snapshot)
  LogicFlow.use(DndPanel)
  LogicFlow.use(SelectionSelect)
  LogicFlow.use(Menu)
  LogicFlow.use(Control)
  LogicFlow.use(SnakerFlowElement, props)
  LogicFlow.use(SnakerFlowAdapter, props)
  LogicFlow.use(Group)
  const defaultConfig: any = {
  }
  if (props.viewer) {
    // defaultConfig.isSilentMode = true
    defaultConfig.adjustEdge = false
    defaultConfig.adjustNodePosition = false
    defaultConfig.stopScrollGraph = true
    defaultConfig.stopMoveGraph = true
    defaultConfig.hoverOutline = false
    defaultConfig.hideAnchors = true
    defaultConfig.adjustNodePosition = false
    defaultConfig.stopMoveGraph = false
    defaultConfig.nodeTextEdit = false
    defaultConfig.edgeTextEdit = false
    defaultConfig.resizeNode = false
    defaultConfig.nodeSelectedOutline = false
  }
  lfInstance.value = new LogicFlow({
    container: unref(lfElRef),
    grid: true,
    ...props.config,
    ...defaultConfig,
    background: props.config.background || {
      backgroundColor: ColorEnum.backgroundColor
    }
  })
  // 实例化成功，方便提供外部注册
  emits('on-init', lfInstance.value)
  // 绑定流程设计器组件属性
  bindWfProps()
  // 初始化操作
  initOp()
  reRender(props.modelValue as ProcessModel)
  // 初始化事件
  initEvent()
}
// 绑定流程设计器组件属性
const bindWfProps = () => {
  const lf = unref(lfInstance)
  if (!lf) return
  lf.graphModel.wfProps = props
}
// 初始化操作
const initOp = () => {
  const lf = unref(lfInstance)
  if (!lf) return
  if (props.viewer) {
    // 预览模式时
    lf.extension.menu.setMenuConfig({
      nodeMenu: [],
      edgeMenu: []
    })
    // 删除上一步
    lf.extension.control.removeItem('undo')
    // 删除下一步
    lf.extension.control.removeItem('redo')
    initDoc()
    return
  }
  // 设置右键菜单
  lf.extension.menu.setMenuConfig({
    nodeMenu: [
      {
        text: '删除',
        callback (node: any) {
          // node为该节点数据
          lf.deleteNode(node.id)
        }
      }
    ]
  })
  // 控制面板-清空画布
  lf.extension.control.addItem({
    iconClass: 'lf-control-clear',
    title: 'clear',
    text: '清空',
    onClick: () => {
      lf.clearData()
    }
  })
  // 控制面板-导出图片
  lf.extension.control.addItem({
    iconClass: 'lf-control-clear',
    title: 'clear',
    text: '导出',
    onClick: () => {
      lf.getSnapshot()
    }
  })
  // 控制面板-添加查看按钮
  lf.extension.control.addItem({
    iconClass: 'lf-control-see',
    title: '',
    text: '查看',
    onClick: () => {
      const dataDetailElRef = unref(dataDetailRef)
      if (!dataDetailElRef) return
      const graphData = getGraphData()
      dataDetailElRef.show(graphData)
    }
  })
  // 控制面板-添加导入按钮
  lf.extension.control.addItem({
    iconClass: 'lf-control-import',
    title: '',
    text: '导入',
    onClick: () => {
      unref(importDataRef).show()
    }
  })
  // 控制面板-设置高亮数据
  lf.extension.control.addItem({
    iconClass: 'lf-control-setting',
    title: '',
    text: '设置高亮',
    onClick: () => {
      unref(highLightDataRef).show()
    }
  })
  // 控制面板-保存
  lf.extension.control.addItem({
    iconClass: 'lf-control-save',
    title: '',
    text: '保存',
    onClick: () => {
      emits('on-save', getGraphData())
    }
  })
  initDoc()
  // 设置默认边
  lf.setDefaultEdgeType('snaker:transition')
  // 二次处理patternItems
  const wrapPatternItems = () => {
    Object.keys(props.wfConfig).forEach(key => {
      if (key in NodeTypeEnum) {
        // @ts-ignore
        Object.keys(props.wfConfig[key]).forEach(nodeConfigKey => {
          if (['type', 'text', 'icon', 'label', 'className', 'properties', 'callback', 'sort'].includes(nodeConfigKey)) {
            // 为节dndPanel的属性，则配置
            patternItems.forEach(item => {
              if (item.type === `snaker:${key}`) {
                if (nodeConfigKey === 'properties') {
                  item[nodeConfigKey] = {
                    ...item[nodeConfigKey],
                    // @ts-ignore
                    ...props.wfConfig[key][nodeConfigKey]
                  }
                } else {
                  // @ts-ignore
                  item[nodeConfigKey] = props.wfConfig[key][nodeConfigKey]
                }
              }
            })
          }
        })
      }
    })
    return patternItems.filter(item => {
      // @ts-ignore
      const nodeConfig = props.wfConfig[item.type.replace('snaker:', '')]
      return nodeConfig?.hide !== true
    })
  }
  lf.extension.dndPanel.setPatternItems([
    ...wrapPatternItems(),
    ...props.wfConfig?.extendDndPanel || []
  ].sort((a, b) => (a.sort || 99) - (b.sort || 99)))
}
// 初始化事件
const initEvent = () => {
  // 初始化事件
  const lf = unref(lfInstance)
  if (!lf) return
  const eventCenter: EventEmitter = lf.graphModel.eventCenter
  // 自定义事件
  if (props.nodeRenderType === 'html') {
    if (props.wfConfig.helpClick && typeof props.wfConfig.helpClick) {
      eventCenter.on('custom:helpClick', props.wfConfig.helpClick)
    }
  }
  if (props.viewer) return
  // 空白区右键事件-弹出流程属性表单
  eventCenter.on('blank:contextmenu', (args) => {
    if (props.wfConfig.blankContextmenu && typeof props.wfConfig.blankContextmenu === 'function') {
      props.wfConfig.blankContextmenu(lf, args)
    } else {
      propertSettingRef.value.show({
        name: lf.graphModel.name,
        displayName: lf.graphModel.displayName,
        expireTime: lf.graphModel.expireTime,
        instanceUrl: lf.graphModel.instanceUrl,
        instanceNoClass: lf.graphModel.instanceNoClass,
        preInterceptors: lf.graphModel.preInterceptors,
        postInterceptors: lf.graphModel.postInterceptors,
        type: 'process'
      })
    }
  })
  // 节点点击事件
  eventCenter.on('node:click', (args) => {
    if (args.data.type === 'snaker:subProcess') {
      // 子流程展开折叠时，不处理
      if (!args.e.srcElement.className.baseVal) {
        return
      }
    }

    currentOpId.value = args.data.id
    // @ts-ignore
    const nodeClick = props.wfConfig[args.data.type.replace('snaker:', '')]?.nodeClick || props.wfConfig.nodeClick
    if (nodeClick && typeof nodeClick === 'function') {
      nodeClick(lf, args)
    } else {
      // html节点特殊处理
      if (props.nodeRenderType === 'html' && [NodeTypeEnum.task].includes(args.data.type.replace('snaker:', ''))) {
        // 为箭头点击事件才处理
        if (args.e.arrowClick) {
          if (props.wfConfig.arrowClick && typeof props.wfConfig.arrowClick === 'function') {
            props.wfConfig.arrowClick(args)
          } else {
            propertSettingRef.value.show({
              ...args.data.properties,
              name: args.data.id,
              displayName: args.data.text?.value || args.data.properties.displayName,
              type: args.data.type
            })
          }
        }
      } else {
        propertSettingRef.value.show({
          ...args.data.properties,
          name: args.data.id,
          displayName: args.data.text?.value || args.data.properties.displayName,
          type: args.data.type
        })
      }
    }
  })
  // 边点击事件
  eventCenter.on('edge:click', (args) => {
    currentOpId.value = args.data.id
    if (props.wfConfig.edgeClick && typeof props.wfConfig.edgeClick === 'function') {
      props.wfConfig.edgeClick(lf, args)
    } else {
      propertSettingRef.value.show({
        ...args.data.properties,
        name: args.data.id,
        displayName: args.data.text?.value,
        type: args.data.type
      })
    }
  })
  // 节点大小改变事件
  lf.on('node:resize', ({ newNodeSize }) => {
    const nodeModel = lf.getNodeModelById(newNodeSize.id)
    if (nodeModel) {
      nodeModel.setProperties({
        width: parseInt(newNodeSize.width),
        height: parseInt(newNodeSize.height)
      })
    }
  })
}
// 重新渲染
const reRender = (data: any): void => {
  const lf = unref(lfInstance)
  if (!lf) return
  lf.render(data)
  // 设置高亮
  setHighLight(props.highLight)
  // 设置参与人加显文本
  setAssigneeText(props.assigneeText)
}
// 处理属性值变化事件
const handlePropertyChange = (e: PropertyEvent) => {
  const lf = unref(lfInstance)
  if (!lf) return
  if (e.type === NodeTypeEnum.process) {
    // 流程属性
    lf.graphModel[e.propertyName] = e.propertyValue
  } else if (e.type === NodeTypeEnum.subProcess) {
    // 子流程属性
    const nodeId = unref(currentOpId)
    if (e.propertyName === 'name') {
      // 更新唯一标识
      if (!lf.getNodeModelById(e.propertyValue)) {
        lf.changeNodeId(nodeId, e.propertyValue)
        currentOpId.value = e.propertyValue
      }
    } else {
      lf.setProperties(nodeId, {
        [e.propertyName]: e.propertyValue
      })
    }
  } else if (([
    NodeTypeEnum.custom,
    NodeTypeEnum.decision,
    NodeTypeEnum.end,
    NodeTypeEnum.fork,
    NodeTypeEnum.join,
    NodeTypeEnum.start,
    NodeTypeEnum.task,
    NodeTypeEnum.wfSubProcess
  ] as NodeTypeEnum []).includes(e.type)) {
    // 节点属性
    const nodeId = unref(currentOpId)
    // 节点信息
    if (e.propertyName === 'name') {
      // 更新唯一标识
      if (!lf.getNodeModelById(e.propertyValue)) {
        lf.changeNodeId(nodeId, e.propertyValue)
        currentOpId.value = e.propertyValue
      }
    } else if (e.propertyName === 'displayName') {
      // 更新节点文本值
      lf.updateText(nodeId, e.propertyValue)
    } else if (e.propertyName === 'width' && [NodeTypeEnum.task, NodeTypeEnum.custom].includes(e.type)) {
      // 宽度
      lf.setProperties(nodeId, {
        width: (Number.isNaN(e.propertyValue) ? 120 : e.propertyValue) as number
      })
      const nodeModel = lf.getNodeModelById(nodeId)
      if (nodeModel) {
        nodeModel.width = (Number.isNaN(e.propertyValue) ? 120 : e.propertyValue) as number
      }
    } else if (e.propertyName === 'height' && [NodeTypeEnum.task, NodeTypeEnum.custom].includes(e.type)) {
      // 高度
      lf.setProperties(nodeId, {
        height: (Number.isNaN(e.propertyValue) ? 120 : e.propertyValue) as number
      })
      const nodeModel = lf.getNodeModelById(nodeId)
      if (nodeModel) {
        nodeModel.height = (Number.isNaN(e.propertyValue) ? 120 : e.propertyValue) as number
      }
    } else if (e.propertyName === 'field') {
      // 更新扩展属性
      lf.setProperties(nodeId, {
        field: JSON.stringify(e.propertyValue)
      })
    } else {
      // 更新基础属性
      lf.setProperties(nodeId, {
        [e.propertyName]: e.propertyValue
      })
    }
    emits('update:modelValue', getGraphData())
  } else if (e.type === NodeTypeEnum.transition) {
    // 边属性
    const edgeId = unref(currentOpId)
    if (e.propertyName === 'name') {
      // 更新唯一标识
      if (!lf.getEdgeModelById(e.propertyValue)) {
        lf.changeEdgeId(edgeId, e.propertyValue)
        currentOpId.value = e.propertyValue
      }
    } else if (e.propertyName === 'displayName') {
      // 更新节点文本值
      lf.updateText(edgeId, e.propertyValue)
    } else {
      // 更新基础属性
      lf.setProperties(edgeId, {
        [e.propertyName]: e.propertyValue
      })
    }
  }
}
/**
 * 获取流程数据
 */
const getGraphData = () => {
  const lf = unref(lfInstance)
  if (!lf) return {}
  return lf.getGraphData()
}
/**
 * 处理导入提交事件
 */
const handleImportSubmit = (str: string) => {
  let data :any = null
  try {
    data = JSON.parse(str)
  } catch {
    try {
      data = snakerXml2LogicFlowJson(str)
    } catch {
      return
    }
  }
  if (data && data.nodes && data.edges) {
    reRender(data)
  }
}
// 导入json
const importJson = (data:any) => {
  reRender(data)
}
// 导入xml
const importXml = (xml:any) => {
  reRender(xml)
}
// 刷新导入下拉数据
const refreshImport = () => {
  return unref(importDataRef)?.refresh()
}
// 导入高亮数据
const handleHighLightSubmit = (jsonStr: string) => {
  let data: any = null
  try {
    data = JSON.parse(jsonStr)
  } catch {
  }
  setHighLight(data)
}
/**
 * 设置高亮数据
 * @param data { "historyNodeNames": [], "historyEdgeNames": [], "activeNodeNames": []}
*/
const setHighLight = (data: any) => {
  const lf = unref(lfInstance)
  if (!lf) return
  // 设置历史节点
  if (data && data.historyNodeNames) {
    data.historyNodeNames.forEach((nodeName: string) => {
      lf.setProperties(nodeName, { state: 'history' })
    })
  }
  // 设置当前节点
  if (data && data.activeNodeNames) {
    data.activeNodeNames.forEach((nodeName: string) => {
      lf.setProperties(nodeName, { state: 'active' })
    })
  }
  // 设置历史边
  if (data && data.historyEdgeNames) {
    data.historyEdgeNames.forEach((edgeName: string) => {
      const edgeModel = lf.getEdgeModelById(edgeName)
      if (edgeModel) {
        edgeModel.setProperties({ state: 'history' })
      }
    })
  }
}
// 设置节点参与人文本
const setAssigneeText = (data: any, kvConfig = { valueKey: 'value', labelKey: 'label' }) => {
  const lf = unref(lfInstance)
  if (!lf || !data) return
  if (Array.isArray(data)) {
    data.forEach((item: any) => {
      lf.setProperties(item[kvConfig.valueKey], { assigneeText: item[kvConfig.labelKey] })
    })
  } else {
    Object.keys(data).forEach(key => {
      lf.setProperties(key, { assigneeText: data[key] })
    })
  }
}
onMounted(init)
// 导出提供给外部操作-$refs.xxx
defineExpose({
  getGraphData,
  refreshImport,
  importJson,
  importXml,
  setHighLight,
  setAssigneeText,
  getLfInstance: () => lfInstance.value
})
</script>
<style scoped>
.m-snakerflow-designer {
  width: 100%;
  height: 100%;
  position: relative;
}
</style>
<style>
.lf-control-see {
    background-image:url('');
}
.lf-control-import {
    background-image:url('');
}
.lf-control-clear {
  background-image:url('');
}
.lf-control-save {
  background-image:url('');
}
.lf-control-mayu {
  background-image:url('');
}
.lf-control-join {
  width: 70px;
  height: 55px;
  background-repeat: no-repeat;
  background-size: 35px 30px;
  background-position: center;
  background-image: url('');
}
.lf-control-fork {
  width: 70px;
  height: 55px;
  background-repeat: no-repeat;
  background-size: 35px 30px;
  background-position: center;
  background-image: url('');
}
.lf-control-setting {
  width: 70px;
  height: 55px;
  background-repeat: no-repeat;
  background-size: 35px 30px;
  background-position: center;
  background-image: url('');
}
</style>
